/**
 * 
 */
package org.ezaero.jmimic.impl.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import org.ezaero.jmimic.api.MimicConfig;
import org.ezaero.jmimic.api.checker.InvocationCallCheckException;
import org.ezaero.jmimic.api.checker.InvocationResponseCheckException;
import org.ezaero.jmimic.impl.store.Invocation;
import org.ezaero.jmimic.impl.store.MimicStore;

public class MimicHandler<T> implements InvocationHandler {

    private final T delegate;
    private final MimicStore store;
    private final boolean pushMode;
    private final MimicConfig config;

    public MimicHandler(MimicConfig config, MimicStore store, T delegate, boolean pushMode) {
        this.config = config;
        this.store = store;
        this.delegate = delegate;
        this.pushMode = pushMode;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] actualArgs) throws Throwable {
        if(method.getName().equals("hashCode") || method.getName().equals("equals")) {
            return method.invoke(delegate, actualArgs);
        }
        
        final String actualMethodSig = method.toString();

        if (store.isRecording()) {
            try {
                final Object rval = method.invoke(delegate, actualArgs);
                store.write(actualMethodSig, actualArgs, rval);
                return rval;
            } catch (Throwable t) {
                store.write(actualMethodSig, actualArgs, t);
                throw t;
            }
        } else {
            final Invocation invocation = store.read(actualMethodSig, actualArgs);
            final String expectedMethod = invocation.method;
            final Object[] expectedArgs = invocation.args;
            final Object expectedResponse = invocation.rval;
            
            if (config.isInvocationCheckEnabled() && !config.getInvocationChecker().checkInvocation(actualMethodSig, actualArgs, expectedMethod, expectedArgs)) {
                throw new InvocationCallCheckException(actualMethodSig, actualArgs, expectedMethod, expectedArgs);
            }

            if (pushMode) {
                try {
                    final Object actualResponse = method.invoke(delegate, actualArgs);
                    
                    if (config.isInvocationCheckEnabled()) {
                        if(!config.getInvocationChecker().checkResponse(actualMethodSig, actualResponse, expectedMethod, expectedResponse)) {
                            throw new InvocationResponseCheckException(actualMethodSig, actualResponse, expectedMethod, expectedResponse);
                        }
                    }
                } catch (Throwable t) {
                    if(!config.getInvocationChecker().checkResponse(actualMethodSig, t, expectedMethod, expectedResponse)) {
                        throw new InvocationResponseCheckException(actualMethodSig, t, expectedMethod, expectedResponse);
                    }
                }
            } else {
                if (expectedResponse instanceof Throwable) {
                    throw ((Throwable) expectedResponse);
                }
            }

            return expectedResponse;
        }
    }
}